package mac10.roles;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import de.dailab.jiactng.agentcore.ontology.IAgentDescription;

import mac10.connection.MessageConstants;
import mac10.models.Cell;
import mac10.models.Point;
import mac10.models.WorldModel;
import mac10.util.AStarHeap;
import mac10.util.Node;

public abstract class AbstractRole {

	protected WorldModel worldModel;
	protected String lastTask = "stay";
	protected LinkedList<String> actions;
	protected String lastAction;
	protected boolean avoidAgents, avoidCows, avoidOpenFences, avoidClosedFences;
	protected IAgentDescription thisAgent;
	protected int simulationStep = -1;
	protected Point lastTarget = null;
	protected Point lastStepTarget = null;

	public AbstractRole(IAgentDescription thisAgent, WorldModel worldModel,
			boolean avoidAgents, boolean avoidCows, boolean avoidOpenFences, boolean avoidClosedFences) {
		this.thisAgent = thisAgent;
		this.worldModel = worldModel;
		this.avoidAgents = avoidAgents;
		this.avoidCows = avoidCows;
		this.avoidOpenFences = avoidOpenFences;
        this.avoidClosedFences = avoidClosedFences;
	}

	public void advanceStep(int simulationStep) {
		this.simulationStep = simulationStep;
	}

	public void setWorldModel(WorldModel worldModel) {
		this.worldModel = worldModel;
	}

	public String getLastTask() {
		return lastTask;
	}

	public void setLastTask(String lastTask) {
		this.lastTask = lastTask;
	}

	public Point getLastTarget() {
		return lastTarget;
	}

	public List<String> goTo(int startx, int starty, int endx, int endy) {

		return null;
	}

	// A Star und start und ende vertauschen
	public static LinkedList<String> nodeToActionStrings(Node goal)
			 {
		LinkedList<String> list = new LinkedList<String>();
		Node current = goal;

		while (current.getPredecessor() != null) {
			Node pre = current.getPredecessor();
			String s = "ERROR";

			if (pre.getX() == current.getX() && pre.getY() > current.getY())
				s = MessageConstants.ACTION_NORTH;
			else if (pre.getX() == current.getX() && pre.getY() < current.getY())
				s = MessageConstants.ACTION_SOUTH;
			else if (pre.getX() < current.getX() && pre.getY() == current.getY())
				s = MessageConstants.ACTION_EAST;
			else if (pre.getX() > current.getX() && pre.getY() == current.getY())
				s = MessageConstants.ACTION_WEST;

			else if (pre.getX() < current.getX() && pre.getY() > current.getY())
				s = MessageConstants.ACTION_NORTHEAST;
			else if (pre.getX() < current.getX() && pre.getY() < current.getY())
				s = MessageConstants.ACTION_SOUTHEAST;
			else if (pre.getX() > current.getX() && pre.getY() < current.getY())
				s = MessageConstants.ACTION_SOUTHWEST;
			else if (pre.getX() > current.getX() && pre.getY() > current.getY())
				s = MessageConstants.ACTION_NORTHWEST;

			list.addFirst(s);

			// System.out.println(s + " " +pre+ " -> " + current);
			if (Math.abs(pre.getX() - current.getX()) > 1
					|| Math.abs(pre.getY() - current.getY()) > 1)
				System.err.println("Knoten abstand ist grösser als eins");

			current = pre;

		}

		return list;
	}

	public Cell getFirstFenceCellOnPath(Node goal) {
		Cell fence = null;
		Node curr = goal;
		if (curr == null)
			return null;
		while (curr.getPredecessor() != null) {
			Cell cell = worldModel.getCell(curr.getX(), curr.getY());
			if (cell.isFence())
				fence = cell;
			curr = curr.getPredecessor();
		}
		Cell cell = worldModel.getCell(curr.getX(), curr.getY());
		if (cell.isFence())
			fence = cell;

		return fence;
	}

	public String executeTask(WorldModel worldModel, String task, int posx,
			int posy, long deadline) {
		this.worldModel = worldModel;
		this.setLastTask(task);
		if (task.equals("stay"))
		{
			return MessageConstants.ACTION_SKIP;
		}
		else if (task.contains("goto"))
		{
			String[] coords;
			try
			{
				coords = task
						.substring(task.indexOf("goto") + 4, task.length())
						.trim().split(",");
			}
			catch (Exception e) {
				return MessageConstants.ACTION_SKIP;
			}
			int goalx = Integer.parseInt(coords[0]);
			int goaly = Integer.parseInt(coords[1]);
			Cell target = this.worldModel.getCell(goalx, goaly);
			String action = this.getAction(posx, posy, deadline, target);
			lastTarget = target;
			return action;
		}
		else
		{
			return this.executeTask(worldModel, task, posx, posy, deadline,
					true);
		}
	}

	public abstract String executeTask(WorldModel worldModel, String task,
			int posx, int posy, long deadline, boolean custom);

	protected String randomAction() {
		Random rand = new Random();
		int i = (int) (rand.nextDouble() * 9);
		String s;
		switch (i) {
		case 1:
			s = MessageConstants.ACTION_NORTH;
			break;
		case 2:
			s = MessageConstants.ACTION_NORTHEAST;
			break;
		case 3:
			s = MessageConstants.ACTION_EAST;
			break;
		case 4:
			s = MessageConstants.ACTION_SOUTHEAST;
			break;
		case 5:
			s = MessageConstants.ACTION_SOUTH;
			break;
		case 6:
			s = MessageConstants.ACTION_SOUTHWEST;
			break;
		case 7:
			s = MessageConstants.ACTION_WEST;
			break;
		case 8:
			s = MessageConstants.ACTION_NORTHWEST;
			break;
		default:
			s = MessageConstants.ACTION_SKIP;
			break;
		}
		return s;
	}

	// melde passietre fences ab
	protected String getAction(int posx, int posy, long deadline, Point target) {
		//register with WorldModel that this is my target
		WorldModel.staticWorldModel.agentTargets.put(thisAgent, target);
		//if last move was a success remove the action from the list
		if (lastMoveSuccessfull(posx, posy) && this.actions != null) {
			if(!this.actions.isEmpty()) this.actions.removeFirst();
		}
		String action = "";
		int goalx = target.getX();
		int goaly = target.getY();

		LinkedList<String> actions = findPath(worldModel, posx, posy, goalx,
				goaly, deadline, avoidAgents, avoidCows, avoidOpenFences, avoidClosedFences);
		/* if(actions != null) */this.actions = actions;

		if (this.actions == null)
			action = MessageConstants.ACTION_SKIP;
		else if (this.actions.size() > 0)
			action = this.actions.getFirst();
		else
			action = MessageConstants.ACTION_SKIP;
		
		
		//abmelden von fences
		Cell positon = this.worldModel.getCell(posx, posy);
		if(positon.isFence())
		{
			MasterSwitcher.staticMasterSwitcher.deregister(this.thisAgent, positon);
		}
			
		this.lastStepTarget = stringMoveToPointTarget(action, posx, posy);
		return action;
	}

	/**
	 * Meldet Fences an.
	 * @param worldModel
	 * @param startx
	 * @param starty
	 * @param goalx
	 * @param goaly
	 * @param deadline
	 * @param avoidAgents
	 * @param avoidCows
	 * @param avoidFences
	 * @return
	 */
	// TODO parameterListe überarbeiten auf Cell oder point umstellen
	public LinkedList<String> findPath(WorldModel worldModel, int startx,
			int starty, int goalx, int goaly, long deadline,
			boolean avoidAgents, boolean avoidCows, boolean avoidOpenFences, boolean avoidClosedFences)
	{
		Point start = new Point(startx, starty);
		Point goal = new Point(goalx, goaly);
		if (start.equals(goal))
		{
		    LinkedList<String> skip = new LinkedList<String>();
		    skip.add(MessageConstants.ACTION_SKIP);
			return skip;
		}
		if (goal.equals(this.lastTarget)) 
		{
			if (checkPath(this.actions, startx, starty, 2))
			{
				return this.actions;
			}
		}
		//Neues Ziel deshalb von allen switchen abmelden
		

		MasterSwitcher.staticMasterSwitcher.deregister(thisAgent);
        this.lastTarget = goal;
		
		AStarHeap astar = new AStarHeap(worldModel, start, goal);
		astar.setAvoidAgents(avoidAgents);
		astar.setAvoidCows(avoidCows);
		astar.setAvoidOpenFences(avoidOpenFences);
        astar.setAvoidClosedFences(avoidClosedFences);
//		long time = System.currentTimeMillis();
		Node to = astar.AStar(deadline);
		// Node to = AStarHeapStatic.AStar(this.worldModel,start, goal);
//		System.err.println("Time needed for A*: " +
//				(System.currentTimeMillis()-time));
		LinkedList<String> actions = null;
		try {
			actions = AbstractRole.nodeToActionStrings(to);
		} catch (Exception e) {
			actions = null;
		}
		
		// Anmeldung des ersten fence auf dem Weg
		Cell fence = getFirstFenceCellOnPath(to);
		if (fence != null)
		{
		    MasterSwitcher.staticMasterSwitcher.register(thisAgent, fence);
		}
		
		return actions;
	}

	protected boolean checkPath(LinkedList<String> actions, int posx, int posy,
			int searchLength) 
	{
	    if(searchLength < 0)
	        throw new IllegalArgumentException("negative searchLength");
	    
		if (actions == null || actions.size() == 0)
			return false;
		
		int depth = (searchLength < actions.size())? searchLength : actions.size();
		int x = posx, y = posy;
		for (int i = 0; i < depth; i++) {
			String action = actions.get(i);
			if (action.equals(MessageConstants.ACTION_NORTH))
				y--;
			if (action.equals(MessageConstants.ACTION_NORTHEAST)) {
				y--;
				x++;
			}
			if (action.equals(MessageConstants.ACTION_EAST))
				x++;
			if (action.equals(MessageConstants.ACTION_SOUTHEAST)) {
				y++;
				x++;
			}
			if (action.equals(MessageConstants.ACTION_SOUTH))
				y++;
			if (action.equals(MessageConstants.ACTION_SOUTHWEST)) {
				y++;
				x--;
			}
			if (action.equals(MessageConstants.ACTION_WEST))
				x--;
			if (action.equals(MessageConstants.ACTION_NORTHWEST)) {
				y--;
				x--;
			}
			if (!this.worldModel.getCell(x, y).isAccessible())
				return false;
		}
		return true;
	}
	
	protected boolean lastMoveSuccessfull(int posx, int posy) {
		if(this.lastStepTarget == null) return false;
		return this.lastStepTarget.equals(this.worldModel.getCell(posx, posy));
	}

	protected Point stringMoveToPointTarget(String action, int posx, int posy) {
		if (action.equals(MessageConstants.ACTION_NORTH))
			return this.worldModel.getCell(posx, posy-1);
		else if (action.equals(MessageConstants.ACTION_NORTHEAST))
			return this.worldModel.getCell(posx+1, posy-1);
		else if (action.equals(MessageConstants.ACTION_EAST))
			return this.worldModel.getCell(posx+1, posy);
		else if (action.equals(MessageConstants.ACTION_SOUTHEAST))
			return this.worldModel.getCell(posx+1, posy+1);
		else if (action.equals(MessageConstants.ACTION_SOUTH))
			return this.worldModel.getCell(posx, posy+1);
		else if (action.equals(MessageConstants.ACTION_SOUTHWEST))
			return this.worldModel.getCell(posx-1, posy+1);
		else if (action.equals(MessageConstants.ACTION_WEST))
			return this.worldModel.getCell(posx-1, posy);
		else if (action.equals(MessageConstants.ACTION_NORTHWEST))
			return this.worldModel.getCell(posx-1, posy-1);

		return this.worldModel.getCell(posx, posy);
	}

	public abstract String toString();

}
